/**
 * File: ProcessSummary.java
 * Created by: mhaimel
 * Created on: 5 Jun 2009
 * CVS:  $Id: ProcessSummary.java,v 1.2 2009/06/24 12:11:16 mhaimel Exp $
 */
package uk.ac.ebi.velvet.processor;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Map.Entry;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.ac.ebi.curtain.utils.data.FileType;
import uk.ac.ebi.curtain.utils.data.ReadType;
import uk.ac.ebi.curtain.utils.file.EntryMapper;
import uk.ac.ebi.curtain.utils.file.EntryMapperBean;
import uk.ac.ebi.curtain.utils.file.FileInfo;
import uk.ac.ebi.curtain.utils.io.MultipleFileHandler;
import uk.ac.ebi.curtain.utils.io.MultipleFileHandler.FileAccess;
import uk.ac.ebi.curtain.utils.io.impl.BufferedRaf;
import uk.ac.ebi.curtain.utils.io.impl.FileIO;
import uk.ac.ebi.velvet.model.Supercontig;
import uk.ac.ebi.velvet.util.io.ReadException;
import uk.ac.ebi.velvet.util.io.WriteException;

/**
 * @author mhaimel
 *
 */
public class ProcessSummary {
	private Log log = LogFactory.getLog(this.getClass());
	private Supercontig sc;
	private MultipleFileHandler<BufferedRaf, FileInfo> readHanderl =
		new MultipleFileHandler<BufferedRaf, FileInfo>(new FileAccess<BufferedRaf>(){
			@Override
			public BufferedRaf openFile(File file) throws IOException {
				return new FileIO(file).getBufferedRafReader();
			}});
	private MultipleFileHandler<PrintWriter, FileInfo> writeHanderl =
		new MultipleFileHandler<PrintWriter, FileInfo>(new FileAccess<PrintWriter>(){
			@Override
			public PrintWriter openFile(File file) throws IOException {
				return new FileIO(file).getPrintWriter();
			}});

	private Map<Integer, FileInfo> fileMap = new HashMap<Integer, FileInfo>();
	private Map<Integer, Integer> file2Category = new HashMap<Integer, Integer>();
	private Map<Integer, Integer> cat2InsertLen = new HashMap<Integer, Integer>();
	private Map<Integer, FileInfo> cat2FileInfo = new HashMap<Integer, FileInfo>();
	private FileInfo contigDumpFile = null;
	
	public ProcessSummary(Supercontig sc) {
		this.sc = sc;
	}

	public static final char TYPE_CONTIG = 'C';
	public static final char TYPE_READ = 'R';
	public static final char TYPE_FILE = 'F';
	public static final char TYPE_INSERT_LENGTH = 'I';
	public void write(){
		getWriteHandler().addFile(getContigDumpFile().getFile(), getContigDumpFile());
		File sum = getSummaryFile();
		BufferedReader in = null;
		try {
			in = new FileIO(sum).getBufferedReader();
			String line = StringUtils.EMPTY;
			while((line = in.readLine()) != null){
				if(StringUtils.isNotBlank(line)){
					char type = line.charAt(0);
					switch (type) {
					case TYPE_FILE:
						addFile(line);
						break;
					case TYPE_INSERT_LENGTH:
						addInsertLength(line);
						break;
					case TYPE_CONTIG:
					case TYPE_READ:
						writeEntry(type,line);
						break;
					default:
						break;
					}
				}
			}
			flush();
		} catch (IOException e) {
			throw new ReadException("Problems reading from file " + sum,e);
		}finally{
			IOUtils.closeQuietly(in);
			getWriteHandler().closeAll();
			getReadHandler().closeAll();
		}
	}

	private void flush() {
		flushReads();
	}

	private void writeEntry(char type, String line) {
		String[] arr = line.split("\t");
		int iCnt = 1;
		Integer id = Integer.valueOf(arr[iCnt++]);
		Long pos = Long.valueOf(arr[iCnt++]);
		Integer nEntries = Integer.valueOf(arr[iCnt++]);
		switch (type) {
		case TYPE_CONTIG:
			writeContig(id,pos,nEntries);
			break;
		case TYPE_READ:
			writeRead(id,pos,nEntries);
			break;
		}
	}

	private SortedMap<Integer, SortedSet<Long>> entries2Pos = new TreeMap<Integer, SortedSet<Long>>();
	private Integer fileId = null;
	private FileInfo unpairedReadDumpFile = null;
	
	private void writeRead(Integer id,Long pos, Integer entries) {
		
		if(null != fileId && !fileId.equals(id)){
			flushReads();
		}
		fileId = id;
		// non paired, paired
		SortedSet<Long> posSet = entries2Pos.get(entries);
		if(null == posSet){
			posSet = new TreeSet<Long>();
			entries2Pos.put(entries, posSet);
		}
		posSet.add(pos);
	}

	private void flushReads() {
		if(fileId != null && !entries2Pos.isEmpty()){
			FileInfo inFile = getFileMap().get(fileId);
			Integer cat = getFile2Category().get(fileId);
			FileInfo outFile = 
				null == cat
					?getUnpairedReadDumpFile()
					:getCat2FileInfo().get(cat);
			for(Entry<Integer, SortedSet<Long>> entry : entries2Pos.entrySet()){
				print(inFile,entry.getValue(),entry.getKey(),outFile);
			}
		}
		fileId = null;
		entries2Pos.clear();
	}

	private FileInfo getUnpairedReadDumpFile() {
		if(null == unpairedReadDumpFile){
			unpairedReadDumpFile  = getSuperContig().getReadOutputFile();
			registerReadFile(null, unpairedReadDumpFile);
		}
		return unpairedReadDumpFile;
	}

	private void writeContig(Integer id, Long pos,Integer entries) {
		print(getFileMap().get(id),pos,entries,getContigDumpFile());
	}

	/**
	 * @param inFile
	 * @param pos
	 * @param nEntries
	 * @param outFile
	 */
	private void print(FileInfo inFile, Long pos, int nEntries, FileInfo outFile) {
		print(inFile, Arrays.asList(new Long[]{pos}), nEntries, outFile);
	}
	
	/**
	 * @param inFile
	 * @param posColl
	 * @param nEntries
	 * @param outFile
	 */
	private void print(FileInfo inFile, Collection<Long> posColl, int nEntries, FileInfo outFile) {
		try{
			PrintWriter out = getWriteHandler().getChannel(outFile);
			BufferedRaf in = getReadHandler().getChannel(inFile);
		
			FileWriterMapper mapper = new FileWriterMapper(out, outFile.getFileType());
			for(Long pos : posColl){
				in.seek(pos);
				inFile.getFileType().getParser().parseNEntry(nEntries, in, inFile, mapper);
			}
		} catch (IOException e) {
			throw new WriteException(
					"Problems reading from " + inFile + " / writing to " + outFile,e);
		}
	}

	private void addInsertLength(String line) {
		String[] arr = line.split("\t");
		int iCnt = 1;
		Integer id = Integer.valueOf(arr[iCnt++]);
		Integer cat = Integer.valueOf(arr[iCnt++]);
		Integer ins = Integer.valueOf(arr[iCnt++]);
		getFile2Category().put(id, cat);
		registerInsertLength(cat, ins);
		registerReadFile(cat);
	}

	private void registerInsertLength(Integer cat, Integer ins) {
		getCat2InsertLen().put(cat, ins);
	}

	
	
	private void registerReadFile(Integer cat) {
		FileInfo fInfo = 
			null == cat?
					getUnpairedReadDumpFile()
					:getSuperContig().getPairedReadOutputFile(cat);
		registerReadFile(cat,fInfo);
	}
	
	private void registerReadFile(Integer cat,FileInfo fInfo) {
		if(null != cat){			
			getCat2FileInfo().put(cat, fInfo);
		}
		getWriteHandler().addFile(fInfo.getFile(), fInfo);
	}
	
	

	private void addFile(String line) {
		String[] arr = line.split("\t");
		int iCnt = 1;
		Integer id = Integer.valueOf(arr[iCnt++]);
		String file = arr[iCnt++];
		ReadType rt = ReadType.valueOf(arr[iCnt++]);
		FileType ft = FileType.valueOf(arr[iCnt++]);
		FileInfo fInfo = new FileInfo(file,ft,rt);
		getFileMap().put(id, fInfo);
		getReadHandler().addFile(fInfo.getFile(), fInfo);
	}
	
	private MultipleFileHandler<BufferedRaf, FileInfo> getReadHandler(){
		return readHanderl;
	}
	
//	private PrintWriter getWriter(Integer id){
//		FileInfo fInfo = getSuperContig().getPairedReadOutputFile(id);
//		
//	}
	
	private MultipleFileHandler<PrintWriter,FileInfo> getWriteHandler(){
		return writeHanderl;
	}
	
	public Map<Integer, FileInfo> getFileMap() {
		return fileMap;
	}
	public Map<Integer, FileInfo> getCat2FileInfo() {
		return cat2FileInfo;
	}
	public Map<Integer, Integer> getCat2InsertLen() {
		return cat2InsertLen;
	}
	
	public Map<Integer, Integer> getFile2Category() {
		return file2Category;
	}
	
	private FileInfo getContigDumpFile(){
		if(null == contigDumpFile){
			contigDumpFile  = getSuperContig().getContigDumpFile();
		}
		return contigDumpFile;
	}

	public File getSummaryFile() {
		return getSuperContig().getSummaryOutputFile();
	}

	private Supercontig getSuperContig() {
		return sc;
	}

	private Log getLog() {
		return log;
	}
	
	private class FileWriterMapper implements EntryMapper{
		
		private PrintWriter out;
		private FileType fileType;

		public FileWriterMapper(PrintWriter out, FileType fileType) {
			this.out = out;
			this.fileType = fileType;
		}

		@Override
		public void mapEntry(EntryMapperBean bean) {
			fileType.getWriter().write(out, bean);
		}

	}
}
